#include "ftsm.h"

#define __std_cout__

namespace ftsm
{
    /////////////////////////////
    // Transition map definition
    /////////////////////////////
    std::map<FTSM_States, std::map<FTSM_TransStatus, FTSM_States>> FTSM::transition_map =
    {
        {FTSM_States::INITIALISING,
            {
                {FTSM_TransStatus::INITIALISED, FTSM_States::CONFIGURING},
                {FTSM_TransStatus::INIT_FAILED, FTSM_States::RECOVERING}
            }
        },
        {FTSM_States::CONFIGURING,
            {
                {FTSM_TransStatus::DONE_CONFIGURING, FTSM_States::READY},
                {FTSM_TransStatus::RETRY_CONFIG, FTSM_States::CONFIGURING},
                {FTSM_TransStatus::DONE_RECONFIGURING, FTSM_States::NoneStates}, // depends on the previous state; only performed if the component was already configured before
                {FTSM_TransStatus::FAILED_CONFIG, FTSM_States::STOPPED}
            }
        },
        {FTSM_States::READY,
            {
                {FTSM_TransStatus::RUN, FTSM_States::RUNNING},
                {FTSM_TransStatus::WAIT, FTSM_States::READY},
                {FTSM_TransStatus::RECONFIGURE, FTSM_States::CONFIGURING},
                {FTSM_TransStatus::RECOVER, FTSM_States::RECOVERING}
            }
        },
        {FTSM_States::RUNNING,
            {
                {FTSM_TransStatus::DONE, FTSM_States::READY},
                {FTSM_TransStatus::CONTINUE, FTSM_States::RUNNING},
                {FTSM_TransStatus::RECOVER, FTSM_States::RECOVERING},
                {FTSM_TransStatus::RECONFIGURE, FTSM_States::CONFIGURING}
            }
        },
        {FTSM_States::RECOVERING,
            {
                {FTSM_TransStatus::DONE_RECOVERING, FTSM_States::NoneStates}, // depends on the previous state
                {FTSM_TransStatus::FAILED_RECOVERY, FTSM_States::STOPPED}
            }
        }
    };

    ///////////////////////////////
    // FTSM method definitions
    ///////////////////////////////
    FTSM::FTSM(std::string name, std::vector<std::string> dependencies, int max_recovery_attempts)
        : current_state(FTSM_States::START)
    {
        this->name = name;
        this->dependencies = dependencies;
        this->max_recovery_attempts = max_recovery_attempts;
        this->configured = false;
        this->is_running = true;
        this->is_alive = false;
        this->trans = nullptr;
        this->current_transaction = -1;
    }

    /**
     * Starts the state machine on a background thread
     */
    void FTSM::run()
    {
        if (! this->is_alive) {
            this->current_state = FTSM_States::INITIALISING;
            this->is_alive = true;
            this->sm_thread = std::thread(&FTSM::__manage_sm, this);
        }
        else {
#ifdef __std_cout__
            std::cout << this->name << " already running" << std::endl;
#endif
        }
    }

    /**
     * Stops the state machine
     */
    void FTSM::stop()
    {
        if (this->is_alive) {
            this->is_running = false;
            this->is_alive = false;
            this->sm_thread.join();
        }
        else {
#ifdef __std_cout__
            std::cout << this->name << " cannot be stopped because it is not running yet" << std::endl;
#endif
        }
    }

    /**
     * Performs component initialisation; calls the virtual this->init method and
     * returns a transition constant
     */
    FTSM_TransStatus FTSM::__init()
    {
        FTSM_TransStatus result = this->init();
        return result;
    }

    /**
     * Performs component configuration; calls the virtual this->configuring method
     * and returns a transition constant
     */
    FTSM_TransStatus FTSM::__configuring()
    {
        int attempt_counter = 0;
        FTSM_TransStatus result = None;
        while (attempt_counter < this->max_recovery_attempts &&
                                    result != FTSM_TransStatus::DONE_CONFIGURING) {
#ifdef __std_cout__
            std::cout << "Configuring " << this->name << "; attempt number " <<
                                                (attempt_counter + 1) << std::endl;
#endif
            result = this->configuring();
            attempt_counter += 1;
        }

        if (attempt_counter == this->max_recovery_attempts &&
                                    result != FTSM_TransStatus::DONE_CONFIGURING) {
#ifdef __std_cout__
            std::cout << "Could not configure " << this->name << " after a maximum of " <<
                                    this->max_recovery_attempts << " attempts" << std::endl;
#endif
            return FTSM_TransStatus::FAILED_CONFIG;
        }

        if (!this->configured) {
            this->configured = true;
            return FTSM_TransStatus::DONE_CONFIGURING;
        }
        else {
            return FTSM_TransStatus::DONE_RECONFIGURING;
        }
    }

    /**
     * Indicates component readiness; calls the virtual this->ready method and
     * returns a transition constant when an operation request is received
     */
    FTSM_TransStatus FTSM::__ready()
    {
        FTSM_TransStatus result = this->ready();
        return result;
    }

    /**
     * Performs component operation; calls the virtual this->running method and
     * returns a transition constant
     */
    FTSM_TransStatus FTSM::__running()
    {
        FTSM_TransStatus result = this->running();
        return result;
    }

    /**
     * Performs component recovery; calls the virtual this->recovering method and
     * returns a transition constant
     */
    FTSM_TransStatus FTSM::__recovering()
    {
        int attempt_counter = 0;
        FTSM_TransStatus result = None;
        while (attempt_counter < this->max_recovery_attempts &&
                                    result != FTSM_TransStatus::DONE_RECOVERING) {
#ifdef __std_cout__
            std::cout << "Attempting recovery of " << this->name <<
                            "; attempt number " << (attempt_counter + 1) << std::endl;
#endif
            result = this->recovering();
            attempt_counter += 1;
        }

        if (attempt_counter == this->max_recovery_attempts &&
                                    result != FTSM_TransStatus::DONE_RECOVERING) {
            std::cout << "Could not recover " << this->name << " after a maximum of " <<
                        this->max_recovery_attempts << " recovery attempts" << std::endl;
            return FTSM_TransStatus::FAILED_RECOVERY;
        }
        return result;
    }

    /**
     * Manages the operation of the state machine by calling the appropriate state methods and
     * performing state machine transitions based on the results of the states
     */
    void FTSM::__manage_sm()
    {
        FTSM_TransStatus transition_constant = FTSM_TransStatus::None;

        while (this->current_state != FTSM_States::STOPPED and this->is_running) {
            if (this->current_state == FTSM_States::INITIALISING) {
                transition_constant = this->__init();
            }
            else if (this->current_state == FTSM_States::CONFIGURING) {
                transition_constant = this->__configuring();
            }
            else if (this->current_state == FTSM_States::READY) {
                transition_constant = this->__ready();
            }
            else if (this->current_state == FTSM_States::RUNNING) {
                transition_constant = this->__running();
            }
            else if (this->current_state == FTSM_States::RECOVERING) {
                transition_constant = this->__recovering();
            }
            this->__transition(transition_constant);
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }

        if (this->is_running) {
            this->is_running = false;
        }
    }

    /**
     * Performs a state machine transition as indicated by the given transition constant
     *
     * @param transition an FTSM_TransStatus constant indicating the transition that should be taken
     */
    void FTSM::__transition(FTSM_TransStatus transition)
    {
        FTSM_States new_state = FTSM::transition_map[current_state][transition];
        if (0 == new_state) {
            new_state = this->previous_state;
        }

        if (new_state != this->current_state) {
#ifdef __std_cout__
            std::cout << "State machine transitioning: " <<
                            this->current_state << " -> " << new_state << std::endl;
#endif
        }

        this->previous_state = this->current_state;
        this->current_state = new_state;
    }

} // namespace ftsm
